package edu.gatech.aristotle.random.experiment;

import java.io.IOException;
import java.util.ArrayList;

import net.util.Debug;
import net.util.File_Handler;
import edu.gatech.aristotle.random.ineqalities.ChernoffBound;
import edu.gatech.aristotle.random.util.CONSTANT_EXPERIMENT;
import edu.gatech.aristotle.random.util.CONSTANT_FILE_NAME;
import edu.gatech.aristotle.random.util.RandomnessSelector;
import edu.gatech.aristotle.random.util.Util_FormatHandler;
import edu.gatech.aristotle.random.util.Util_Math;

public class RandomnessExperiment {
	int out_of_prediction	= 0;
	int sampleCounter	= 0;
	
	public String makeSubjectReport(){
		
		StringBuffer report	= new StringBuffer();
		
		report.append( "fileNmae, version, size, failed, failed ratio\n" );
		for ( int i = 0; i < CONSTANT_FILE_NAME.matrixNewFormatFileNames.length; i ++ ){
			ArrayList<String> stringLines	= File_Handler.fileContentToStrings( CONSTANT_FILE_NAME.matrixNewFormatFileNames[i] );
			
			int version_loop	= 1;
			for ( String oneLine : stringLines){
				if ( oneLine.startsWith( "0" ) || oneLine.startsWith( "1" ) ){
					ArrayList<Integer> integers = Util_FormatHandler.oneStringToInteger( oneLine );
					
					int universeSize 	= integers.size();
					int failedSize		= Util_FormatHandler.sumIntegerList( integers );
					double ratio		= Util_Math.rate( failedSize, universeSize );
					
					report.append( CONSTANT_FILE_NAME.matrixNewFormatFileNames[i] + ", " + version_loop + 
										", " + universeSize +
										"," + failedSize +
										"," + ratio  + "\n");
					
					version_loop++;
				}
			}
			
			report.append( "\n" );
		}
		
		try {
			File_Handler.write_to_file( CONSTANT_FILE_NAME.experimentDir + "subject_summery.txt", report.toString() );
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return report.toString();
	}
	/**
	 * Choose one version of each program, and combine them together.
	 * @throws IOException 
	 *
	 */
	public void doChernoffMixedProgramExperiment() throws IOException{
		ArrayList<Integer> mixedProgramIntegers	= new ArrayList<Integer>();
		int versionNumber	= 1;
		
		for ( int i = 0; i < CONSTANT_FILE_NAME.matrixNewFormatFileNames.length; i++ ){
			ArrayList<Integer> oneLineIntegers		= Util_FormatHandler.retriveOneLineIntegers( 
																CONSTANT_FILE_NAME.matrixNewFormatFileNames[i], versionNumber );
			
			mixedProgramIntegers.addAll( oneLineIntegers );
		}

		String experimentReport	= doChernoffExperiment( mixedProgramIntegers, 1 );
		
		StringBuffer experimentReportBuffer	= new StringBuffer();		
		experimentReportBuffer.append( makeReportRowNames() );
		experimentReportBuffer.append( experimentReport );
		
		String reprotFileName	= "dataSource//Experiment_Report_Mix_Program";
		String prefix			= ".txt";
		
		File_Handler.write_to_file( reprotFileName + prefix, 
									experimentReportBuffer.toString() 
								);
		System.out.println( "doChernoffMixedProgramExperiment end." );
	}
	
	/**
	 * Run all versions of each programs.
	 * @throws IOException
	 */
	public void doChernoffBatchProgramExperiment() throws IOException{
		
		StringBuffer experimentReportBuffer	= new StringBuffer();		
		experimentReportBuffer.append( makeReportRowNames() );
		
		for ( int experimentCounter = 1; 
					experimentCounter <= CONSTANT_EXPERIMENT.BATCH_PROGRAM_EXPERIMENT_TIMES; 
					experimentCounter++ )
		{
			Debug.println( "===============" + experimentCounter + " Times================");
			for ( int i = 0; i < CONSTANT_FILE_NAME.matrixNewFormatFileNames.length; i++ ){
				experimentReportBuffer.append( CONSTANT_FILE_NAME.matrixNewFormatFileNames[i] + "\n" );
				
				String oneProgramReport = doChernoffBatchVersionExperiment( CONSTANT_FILE_NAME.matrixNewFormatFileNames[i] );
				
				experimentReportBuffer.append( oneProgramReport + "\n" );
				
			}
		}
		

		File_Handler.write_to_file( CONSTANT_FILE_NAME.BATCH_PROGRAM_RERORT_NAME, 
									experimentReportBuffer.toString() 
								);
		
		System.out.println( "TOTAL OUT OF PREDICITON: " + this.out_of_prediction + ", # sample: " + sampleCounter );
	}
	
	/**
	 * For a specified file, there are N lines, each of which is the result of one version of test case.
	 * 
	 * For each of such a line (version), there is a real rate of passed test cases P.
	 * 
	 * The expriement, for each version, randomly chooses a subset, 
	 * computes the up prediction p1 and low prediction p2, 
	 * and then check whether P is within the range of p1 to p2: If yes, oupt 1; otherwise 0.
	 * 
	 * 
	 * @param fileName
	 * @throws IOException 
	 */
	public String doChernoffBatchVersionExperiment( String fileName ) throws IOException{		
		String reprotFileName	=  fileName + "_report";

		File_Handler.append_to_file( reprotFileName, makeReportRowNames() );		
		File_Handler.append_to_file( reprotFileName, "deviation: " + CONSTANT_EXPERIMENT.DEVIATION +
													", sample size: " + CONSTANT_EXPERIMENT.RANDOM_SIZE + "\n");
		
		ArrayList<String> fileContent = File_Handler.fileContentToStrings( fileName );
		StringBuffer experimentReport	= new StringBuffer();
		
		int lineNumber	= 1;
		
		for ( String oneLine : fileContent ){
			if ( ( oneLine.startsWith( "1" ) || oneLine.startsWith( "0" ) ) ){
				//Debug.println( "experiment " + lineNumber );
				String oneVersionReport	= doChernoffExperiment( oneLine, lineNumber++ );
				
				experimentReport.append( oneVersionReport );
				
				sampleCounter += oneLine.length();
			}
		}
		
		//Debug.println( "Experiment of " + fileName + " finished!\n" );
		
		
		File_Handler.append_to_file( reprotFileName, experimentReport.toString() );
		
		Debug.println( "experiment " + fileName + " finished" );
		
		return experimentReport.toString();
	}
	
	private String makeReportRowNames(){
		return "version, universe size, universe ratio, " +
						"sample size, sample ratio, " +
						"lower prediction, uppper prediction, "  + "lower bound, " + "upper bound, "+
						"correctness" + "\n\n";
	}
	
	/**
	 * 
	 * @param oneLine	one line of string that contains the testing results.
	 * @return experiment report which format is
	 * 
	 * version, universe size, universe ratio, sample size, sample ratio, lower prediction, uppper prediction, correctness.
	 */
	public String doChernoffExperiment( String oneLine, int versionNumber ){	
		ArrayList<Integer> oneLineIntegers	= Util_FormatHandler.oneStringToInteger( oneLine );	
		
		return doChernoffExperiment( oneLineIntegers, versionNumber );
	}
	
	/**
	 * 
	 * @param FileName
	 * @param lineNumber
	 * @param versionNumber the same as line number
	 * 
	 * @return
	 */
	public String doChernoffExperiment( String fileName, int lineNumber, int versionNumber ){
		ArrayList<Integer> oneLine	= Util_FormatHandler.retriveOneLineIntegers( fileName, lineNumber );
		
		return doChernoffExperiment( oneLine, versionNumber );
	}
	
	public String doChernoffExperiment( ArrayList<Integer> oneLineIntegers, int versionNumber ){
		//Debug.println( "oneLineIntegers: " + oneLineIntegers );
		
		int universeSize	= oneLineIntegers.size();
		int universePassed	= universeSize - Util_FormatHandler.sumIntegerList( oneLineIntegers );
		float universeRatio	= Util_Math.rate( universePassed, universeSize );
		
		ArrayList<Integer> randomSubsetInteger	= RandomnessSelector.selectRandomSubset
																( oneLineIntegers, CONSTANT_EXPERIMENT.RANDOM_SIZE );
		int randomPassed		= CONSTANT_EXPERIMENT.RANDOM_SIZE 
											- Util_FormatHandler.sumIntegerList( randomSubsetInteger );	
		float randomSetRatio	= Util_Math.rate( randomPassed, CONSTANT_EXPERIMENT.RANDOM_SIZE );
		
		ChernoffBound chernoffBound = new ChernoffBound( CONSTANT_EXPERIMENT.DEVIATION, 
														randomPassed, 
														CONSTANT_EXPERIMENT.RANDOM_SIZE );
		
		//chernoffBound.makePredictionReport();
		
		double upperPrediction	= chernoffBound.doUpperEstimation();
		double lowerPrediction	= chernoffBound.doLowerEstimation();
		//double errorBound		= chernoffBound.doBound();
		double lowerBound		= 1 - chernoffBound.doLowerConfidenceBound();
		double upperBound		= 1 - chernoffBound.doUpperConfidenceBound();
		
		int correctness = 0;
		
		if ( (universeRatio== 1) ){
			correctness = 1;
		} else if ( universeRatio > lowerPrediction && universeRatio < upperPrediction ){
			correctness	= 1;
		}
		
		if ( correctness == 0 )
			this.out_of_prediction++;
		
		StringBuffer experimentReport	= new StringBuffer();
		experimentReport.append( versionNumber + ", ");
		experimentReport.append( universeSize + ", ");
		experimentReport.append( universeRatio + ", ");
		experimentReport.append( CONSTANT_EXPERIMENT.RANDOM_SIZE + ", ");
		experimentReport.append( randomSetRatio + ", ");
		experimentReport.append( lowerPrediction + ", ");
		experimentReport.append( upperPrediction + ", ");
		experimentReport.append( upperBound + ", ");	
		experimentReport.append( lowerBound + ", " );	
		experimentReport.append( "c" + correctness);	
		experimentReport.append( "\n" );		
		
		return experimentReport.toString();
	}
	
	public void doSingleChernoffExperiment( String fileName, int aVersionNumber ){
		//Debug.println( "============Experiment of " + fileName + "==================");
		ArrayList<Integer> oneLineIntegers		= Util_FormatHandler.retriveOneLineIntegers( fileName, aVersionNumber );
		
		int
			universeSize	= oneLineIntegers.size(),
			universePassed	= universeSize - Util_FormatHandler.sumIntegerList( oneLineIntegers );
		
		ArrayList<Integer> randomSubsetInteger	= RandomnessSelector.selectRandomSubset( oneLineIntegers, 
																		CONSTANT_EXPERIMENT.RANDOM_SIZE );
		int 
			randomSize		= randomSubsetInteger.size(),
			randomPassed	= randomSize - Util_FormatHandler.sumIntegerList( randomSubsetInteger );
		
		
		makeReportTitle( fileName, universeSize, universePassed, randomSize, randomPassed );		
		Debug.println();
		new ChernoffBound( CONSTANT_EXPERIMENT.DEVIATION, randomPassed, randomSize).makeReport();
		//Debug.println( "[Experiment of " + fileName + " done!]");
	}
	
	public void doBatchVersionRandomSampleDeviationExperiment( String fileName ) throws IOException{
		ArrayList<String> fileContent = File_Handler.fileContentToStrings( fileName );
		
		StringBuffer report	= new StringBuffer();
		
		for ( int i = 0; i < fileContent.size() - 1; i++ ){// The last line is the summary of the file.			
			String oneLineReport	= doRandomSampleDeviationExperiment( fileContent.get(i) );
			
			report.append( oneLineReport );
			
			Debug.print( i + " line done\n" );
		}

		File_Handler.append_to_file( fileName + "_Deviation_Report" + CONSTANT_EXPERIMENT.RANDOM_SIZE, report.toString() );
		Debug.println( fileName + " doBatchVersionRandomSampleDeviationExperiment done!" );
	}
		
	public String doRandomSampleDeviationExperiment( String lineContent ){
		
		ArrayList<Integer> oneLine	= Util_FormatHandler.oneStringToInteger(lineContent);
		
		return doRandomSampleDeviationExperiment( oneLine );	
	}
	
	/***
	 * For a specified list of integer, do random sampling and check the deviations of the random sampling.
	 *  
	 * Compare with the Chernoff bound. 
	 * 
	 * @param oneLineInteger
	 */
	public String doRandomSampleDeviationExperiment( ArrayList<Integer> oneLineIntegers ){
		int
			universeSize	= oneLineIntegers.size(),
			universePassed	= universeSize - Util_FormatHandler.sumIntegerList( oneLineIntegers );
		double universeRate	= Util_Math.rate(universePassed, universeSize);

		
		StringBuffer report	= new StringBuffer();
		if ( universeRate == 1 ){
			report.append( "\n\nUniverseRate is 1\n\n" );
			
			return report.toString();
		}
				
		int randomSize		= CONSTANT_EXPERIMENT.RANDOM_SIZE;
		
		report.append( "\nUniverse:\t" +  universeSize + "/" + universePassed +  
							";\t Universe rate:\t"  + universeRate + "\n" );
		report.append( "Random sample:\t" + randomSize + ", Deviation: " + CONSTANT_EXPERIMENT.DEVIATION + "\n" );
		
		ChernoffBound chernoffBound	= new ChernoffBound( CONSTANT_EXPERIMENT.DEVIATION, 
																								(int)(universeRate * randomSize), 
																								randomSize );
		report.append( "\tlower prediction: " + chernoffBound.doLowerEstimation() +
						", upper prediction: " + chernoffBound.doUpperEstimation() + 
						"\n");
		
		int numberOfOutPrediction	= 0;
		for ( int experimentTime = 0; experimentTime < CONSTANT_EXPERIMENT.BATCH_VERSION_NUMBER; experimentTime++ ){
			ArrayList<Integer> randomSubsetInteger	= RandomnessSelector.selectRandomSubset( oneLineIntegers, randomSize );
			int randomPassed	= randomSize - Util_FormatHandler.sumIntegerList( randomSubsetInteger );
			
			double sampleRate	= Util_Math.rate( randomPassed, randomSize );
			report.append( "Sample rate: " + sampleRate + ", Sample deviation: " + ( universeRate - sampleRate ) + "\n" );
			
			if ( sampleRate > chernoffBound.doUpperEstimation() || sampleRate < chernoffBound.doLowerEstimation() ){
				numberOfOutPrediction++;
			}
		}
		
		report.append( "NumberOfOutPrediction: " + numberOfOutPrediction );
		if ( numberOfOutPrediction > 0 )
			report.append( ", with universe rate:\t"  + universeRate);
		
		report.append( "\n" );
		
		return report.toString();
	}
	
	private void makeReportTitle( String fileName, int universeSize, int universeSum, int randomSize, int randomSum ){
//		Debug.printCollection( randomSubsetInteger );
		Debug.println( "====File name: " + fileName + "====" );
		Debug.println( "Universe:\t" +  universeSize + "/" + universeSum +  
						";\t Real rate:\t"  + Util_Math.rate(universeSum, universeSize) );
		Debug.println( "Random sample:\t" + randomSize +	"/" + randomSum +  
						";\t Sample rate:\t" + Util_Math.rate( randomSum, randomSize));
	}
}
